#include "W25QXX.h"
#include "string.h"

#define GET_WORD1BYTE(x) (((x) & 0xFF))
#define GET_WORD2BYTE(x) (((x>>8) & 0xFF))
#define GET_WORD3BYTE(x) (((x>>16) & 0xFF))

/**
 * @brief    SPI发送指定长度的数据
 * @param    buf  —— 发送数据缓冲区首地址
 * @param    size —— 要发送数据的字节数
 * @retval   成功返回HAL_OK
 */
static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size)
{
    return HAL_SPI_Transmit(&hspi2, send_buf, size, 100);
}

/**
 * @brief   SPI接收指定长度的数据
 * @param   buf  —— 接收数据缓冲区首地址
 * @param   size —— 要接收数据的字节数
 * @retval  成功返回HAL_OK
 */
static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size)
{
   return HAL_SPI_Receive(&hspi2, recv_buf, size, 10000);
}

/**
 * @brief   读取Flash内部的ID
 * @param   none
 * @retval  成功返回device_id
 */
uint16_t W25QXX_ReadID(void)
{
    uint8_t recv_buf[2] = {0};    //recv_buf[0]存放Manufacture ID, recv_buf[1]存放Device ID
    uint16_t device_id = 0;
    uint8_t send_data[4] = {ManufactDeviceID_CMD,0x00,0x00,0x00};   //待发送数据，命令+地址
    W25QXX_CS_L;/* 使能片选 */
    /* 发送并读取数据 */
    if (HAL_OK == SPI_Transmit(send_data, 4)) 
    {
        if (HAL_OK == SPI_Receive(recv_buf, 2)) 
        {
            device_id = (recv_buf[0] << 8) | recv_buf[1];
        }
    }
    W25QXX_CS_H; /* 取消片选 */
    return device_id;
}


/**
 * @brief     读取W25QXX的状态寄存器，W25Q64一共有2个状态寄存器
 * @param     reg  —— 状态寄存器编号(1~2)
 * @retval    状态寄存器的值
 */
static uint8_t W25QXX_ReadSR(uint8_t reg)
{
    uint8_t result = 0; 
    uint8_t send_buf[4] = {0x00,0x00,0x00,0x00};
    switch(reg)
    {
        case 1:
            send_buf[0] = READ_STATU_REGISTER_1;break;
        case 2:
            send_buf[0] = READ_STATU_REGISTER_2;break;
        default:
            send_buf[0] = READ_STATU_REGISTER_1;break;
    }
    W25QXX_CS_L; /* 使能片选 */
    if (HAL_OK == SPI_Transmit(send_buf, 4)) 
    {
        if (HAL_OK == SPI_Receive(&result, 1)) 
        {
            W25QXX_CS_H;
            return result;
        }
    }
    W25QXX_CS_H;/* 取消片选 */
    return 0;
}

/**
 * @brief	阻塞等待Flash处于空闲状态
 * @param   none
 * @retval  none
 */
uint8_t W25QXX_Wait_Busy(uint32_t timeout)
{
    while((W25QXX_ReadSR(1) & 0x01) == 0x01 && timeout) timeout--; // 等待BUSY位清空
    return 0;
}

/**
 * @brief   读取SPI FLASH数据
 * @param   buffer      —— 数据存储区
 * @param   start_addr  —— 开始读取的地址(最大32bit)
 * @param   nbytes      —— 要读取的字节数(最大65535)
 * @retval  成功返回0，失败返回-1
 */
int W25QXX_Read(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes)
{
    uint8_t cmd[4] = {READ_DATA_CMD, GET_WORD3BYTE(start_addr),GET_WORD2BYTE(start_addr),GET_WORD1BYTE(start_addr)};
	W25QXX_Wait_Busy(0xFFFFFFFF);
     /* 使能片选 */
    W25QXX_CS_L;
    if (HAL_OK == SPI_Transmit(cmd, 4)) 
    { 
        if (HAL_OK == SPI_Receive(buffer, nbytes)) 
        {
            W25QXX_CS_H;
            return 0;
        }
    }
    W25QXX_CS_H;
    return -1;
}

/**
 * @brief    W25QXX写使能,将S1寄存器的WEL置位
 * @param    none
 * @retval
 */
void W25QXX_Write_Enable(void)
{
    uint8_t cmd= WRITE_ENABLE_CMD;
    W25QXX_CS_L;
    SPI_Transmit(&cmd, 1);
    W25QXX_CS_H;
    W25QXX_Wait_Busy(0xFFFFFFFF);
}

/**
 * @brief    W25QXX写禁止,将WEL清零
 * @param    none
 * @retval    none
 */
void W25QXX_Write_Disable(void)
{
    uint8_t cmd = WRITE_DISABLE_CMD;
    W25QXX_CS_L;
    SPI_Transmit(&cmd, 1);
    W25QXX_CS_H;
    W25QXX_Wait_Busy(0xFFFFFFFF);
}

/**
 * @brief    W25QXX擦除一个扇区
 * @param   sector_index    —— 扇区下标 根据实际容量设置
 * @retval  none
 * @note    阻塞操作
 */
void W25QXX_Erase_Sector(uint32_t sector_index)
{
    uint32_t sector_addr = sector_index * 4096;
    uint8_t cmd[4] = {SECTOR_ERASE_CMD, GET_WORD3BYTE(sector_addr),GET_WORD2BYTE(sector_addr),GET_WORD1BYTE(sector_addr)};
    //每个块有16个扇区，每个扇区的大小是4KB，需要换算为实际地址
    W25QXX_Write_Enable();
    W25QXX_Wait_Busy(0xFFFFFFFF);        //等待写使能完成
     /* 使能片选 */
    W25QXX_CS_L;
    SPI_Transmit(cmd, 4);
    W25QXX_CS_H;
    W25QXX_Wait_Busy(0xFFFFFFFF);       //等待扇区擦除完成
}

/**
* @brief : void W25QXX_Erase_Block(uint32_t block_addr, uint8_t mode)
* @param : block_addr —— 块地址，根据实际容量设置
* @param : mode —— 擦除模式，可选择KB32_ERASE_CMD、KB64_ERASE_CMD
* @author : Sheep
* @date : 24/10/29
*/
void W25QXX_Erase_Block(uint32_t block_addr, uint8_t mode)
{
    uint8_t cmd[4] = {mode, GET_WORD3BYTE(block_addr),GET_WORD2BYTE(block_addr),GET_WORD1BYTE(block_addr)};
    W25QXX_Write_Enable();
    W25QXX_Wait_Busy(0xFFFFFFFF);        //等待写使能完成
     /* 使能片选 */
    W25QXX_CS_L;
    SPI_Transmit(cmd, 4);
    W25QXX_CS_H;
    W25QXX_Wait_Busy(0xFFFFFFFF);       //等待扇区擦除完成
}

/**
* @brief : void W25QXX_Erase_Chip(void)
* @author : Sheep
* @date : 24/10/29
*/
void W25QXX_Erase_Chip(void)
{
    uint8_t cmd = CHIP_ERASE_CMD;
    W25QXX_Write_Enable();  //开启写使能
    W25QXX_Wait_Busy(0xFFFFFFFF);        //等待写使能完成
     /* 使能片选 */
    W25QXX_CS_L;
    SPI_Transmit(&cmd, 1);
    W25QXX_CS_H;
    W25QXX_Wait_Busy(0xFFFFFFFF);       //等待扇区擦除完成
}


/**
 * @brief    页写入操作
 * @param    dat —— 要写入的数据缓冲区首地址
 * @param    WriteAddr —— 要写入的地址
 * @param   byte_to_write —— 要写入的字节数（0-256）
 * @attention 每次写满256字节后，下一次的地址最低字节必须为0，否则时钟继续往下，会在这个页面内循环编写
 * @retval    none
 */
void W25QXX_Page_Program(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes)
{
    uint8_t cmd[4] = {PAGE_PROGRAM_CMD, GET_WORD3BYTE(WriteAddr), GET_WORD2BYTE(WriteAddr), GET_WORD1BYTE(WriteAddr),};
    W25QXX_Write_Enable(); //注意在每次写入前都要写使能，因为写完后w25Q64会自动清除这个位
    /* 使能片选 */
    W25QXX_CS_L;
    SPI_Transmit(cmd, 4);
    SPI_Transmit(dat, nbytes);
    W25QXX_CS_H;
    W25QXX_Wait_Busy(0xFFFFFFFF);
}

/**
* @brief : void W25QXX_Write(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes)
* @param : buffer —— 数据缓冲区首地址
* @param : start_addr —— 起始地址
* @param : nbytes —— 要写入的字节数
* @attention : 支持随机读写，比较耗内存，需要4096字节的缓存
* @author : Sheep
* @date : 24/10/29
*/
void W25QXX_Write(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes)
{
    uint8_t temp_buff[4096] = {0}; //每个sector的缓存4KB
    uint16_t write_num = 0; //要写入的数量
    while(nbytes)
    {
        uint32_t sector_index = start_addr / 4096; //所属的sector
        uint32_t offset_addr = start_addr % 4096;  //在sector内的偏移地址
        uint8_t need_erase = 0; //默认不用擦除 
        if(nbytes <= (4096 - offset_addr)) write_num = nbytes;  //写剩下的字节都在一页内
        else write_num = 4096 - offset_addr; //剩下的字节数超出这一页，先写到这页尾部
        W25QXX_Read(temp_buff, sector_index*4096, 4096); //先读出整个扇区
        for (uint16_t i = 0; i < 4096; i++)
        {
            if(temp_buff[i] != 0XFF)
            { //只要有一个不是0xff就说明需要擦除
                need_erase = 1;
                break;
            }
        }
        if(need_erase)W25QXX_Erase_Sector(sector_index);
        //把要写的数据写入扇区缓冲
        memcpy(&temp_buff[offset_addr], buffer, write_num); 
        //写一整个扇区
        for(uint16_t i = 0; i < 4096; i+=256)
        {
            W25QXX_Page_Program(&temp_buff[i],sector_index*4096 + i, 256);
        }
        //更新下次要写的地址和数量和写入的缓冲指针
        nbytes -= write_num; 
        start_addr += write_num;
        buffer += write_num; 
        need_erase = 0; //下一页是否需要擦除的标志位
    }
}



